package com.huahai.manager.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.huahai.manager.websocket.disruptor.PictureEditEventProducer;
import com.huahai.manager.websocket.enums.PictureEditActionEnum;
import com.huahai.manager.websocket.enums.PictureEditMessageTypeEnum;
import com.huahai.manager.websocket.model.PictureEditMessageDTO;
import com.huahai.manager.websocket.model.PictureEditMessageVO;
import com.huahai.pojo.entity.User;
import com.huahai.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 图片编辑消息的处理器
 *
 * @author huahai
 */
@Component
@Slf4j
public class PictureEditHandler extends TextWebSocketHandler {

    @Resource
    private UserService userService;

    @Resource
    private PictureEditEventProducer pictureEditEventProducer;

    // 每张图片的编辑状态，key: pictureId, value: 当前正在编辑的用户 ID
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();

    // 保存所有连接的会话，key: pictureId, value: 用户会话集合
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();


    /**
     * 连接建立成功时候调用
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        // 1. 将用户信息和图片信息保存到 pictureSessions 中
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        User user = (User) session.getAttributes().get("user");
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        pictureSessions.get(pictureId).add(session);
        // 2. 构造响应给前端的 PictureEditMessageVO 对象
        PictureEditMessageVO pictureEditMessageVO = new PictureEditMessageVO();
        pictureEditMessageVO.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("%s 加入编辑", user.getUserName());
        pictureEditMessageVO.setMessage(message);
        pictureEditMessageVO.setUser(userService.getUserVO(user));
        // 3. 对参与该会话的用户发送广播
        broadcastByPictureId(pictureId, pictureEditMessageVO);
    }

    /**
     * 接收到消息时候调用
     *
     * @param session 当前会话
     * @param message 接收到的前端返回的消息
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        // 1. 解析前端发送的消息为 PictureEditMessageDTO 对象
        PictureEditMessageDTO pictureEditMessageDTO = JSONUtil.toBean(message.getPayload(), PictureEditMessageDTO.class);
//        // 转化消息类型为枚举
//        String type = pictureEditMessageDTO.getType();
//        PictureEditMessageTypeEnum pictureEditMessageTypeEnum = PictureEditMessageTypeEnum.valueOf(type);
//
        // 2. 从 session 中获取公共的参数
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        User user = (User) session.getAttributes().get("user");
//
//        // 3. 处理不同状态的消息
//        switch (pictureEditMessageTypeEnum) {
//            case ENTER_EDIT:
//                handleEnterEditMessage(pictureEditMessageDTO, session, pictureId, user);
//                break;
//            case EDIT_ACTION:
//                handleEditActionMessage(pictureEditMessageDTO, session, pictureId, user);
//                break;
//            case EXIT_EDIT:
//                handleExitEditMessage(pictureEditMessageDTO, session, pictureId, user);
//                break;
//            default:
//                // 默认发送错误消息
//                PictureEditMessageVO pictureEditMessageVO = new PictureEditMessageVO();
//                pictureEditMessageVO.setType(PictureEditMessageTypeEnum.ERROR.getValue());
//                pictureEditMessageVO.setMessage("消息类型错误");
//                pictureEditMessageVO.setUser(userService.getUserVO(user));
//                // 获取处理 JSON 精度丢失的序列化器
//                ObjectMapper objectMapper = getObjectMapper();
//                String errorMessage = objectMapper.writeValueAsString(pictureEditMessageVO);
//                session.sendMessage(new TextMessage(errorMessage));
//                break;
//        }

        // 修改为：将接收和发送消息改为将消息生产到 Disruptor 队列中让消费者处理
        // 生产消息
        pictureEditEventProducer.publishEvent(pictureEditMessageDTO, session, pictureId, user);
    }

    /**
     * 连接关闭时候调用
     *
     * @param session 当前会话
     * @param status  关闭状态
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        // 1. 从 session 中获取公共的参数
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        User user = (User) session.getAttributes().get("user");
        // 2. 移除当前用户的编辑状态
        handleExitEditMessage(null, session, pictureId, user);
        // 3. 关闭连接，移除用户会话
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        if (webSocketSessions != null) {
            // 移除用户会话
            webSocketSessions.remove(session);
            if(CollUtil.isEmpty(webSocketSessions)){
                // 删除对当前图片的会话
                pictureSessions.remove(pictureId);
            }
        }
        // 4. 构造响应对象，发送通知
        PictureEditMessageVO pictureEditMessageVO = new PictureEditMessageVO();
        pictureEditMessageVO.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("%s 离开编辑", user.getUserName());
        pictureEditMessageVO.setMessage(message);
        pictureEditMessageVO.setUser(userService.getUserVO(user));
        // 对参与该会话的用户发送广播
        broadcastByPictureId(pictureId, pictureEditMessageVO);
    }

    /**
     * 处理进入编辑状态的消息
     *
     * @param pictureEditMessageDTO 前端发送的消息
     * @param session               当前会话
     * @param pictureId             图片 ID
     * @param user                  当前用户
     */
    public void handleEnterEditMessage(PictureEditMessageDTO pictureEditMessageDTO, WebSocketSession session, Long pictureId, User user) throws Exception {
        // 没有用户在进行图片编辑，才能进入编辑状态
        if (pictureEditingUsers.get(pictureId) == null) {
            // 将用户 id 保存到 pictureEditingUsers 中
            pictureEditingUsers.put(pictureId, user.getId());
            // 构造响应，向会话内的全体用户发送消息
            PictureEditMessageVO pictureEditMessageVO = new PictureEditMessageVO();
            pictureEditMessageVO.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            String message = String.format("%s 开始图片编辑", user.getUserName());
            pictureEditMessageVO.setMessage(message);
            pictureEditMessageVO.setUser(userService.getUserVO(user));
            // 对参与该会话的用户发送广播
            broadcastByPictureId(pictureId, pictureEditMessageVO);
        }
    }

    /**
     * 处理进行的编辑操作的消息
     *
     * @param pictureEditMessageDTO 前端发送的消息
     * @param session               当前会话
     * @param pictureId             图片 ID
     * @param user                  当前用户
     */
    public void handleEditActionMessage(PictureEditMessageDTO pictureEditMessageDTO, WebSocketSession session, Long pictureId, User user) throws Exception {
        // 获取当前编辑者
        Long editingUserId = pictureEditingUsers.get(pictureId);
        // 判断进行的操作是否合理
        String editAction = pictureEditMessageDTO.getEditAction();
        PictureEditActionEnum actionEnum = PictureEditActionEnum.getEnumByValue(editAction);
        if (actionEnum == null) {
            log.error("编辑操作不符合规范");
            return;
        }
        // 判断当前用户是否是当前正在编辑的图片的编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 执行编辑操作，构造响应
            PictureEditMessageVO pictureEditMessageVO = new PictureEditMessageVO();
            pictureEditMessageVO.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
            String message = String.format("%s 执行了 %s", user.getUserName(), actionEnum.getText());
            pictureEditMessageVO.setMessage(message);
            // 设置图片编辑操作
            pictureEditMessageVO.setEditAction(editAction);
            pictureEditMessageVO.setUser(userService.getUserVO(user));
            // 向会话内的全体用户发送消息，排除当前操作的用户，防止操作重复
            broadcastByPictureId(pictureId, pictureEditMessageVO, session);
        }
    }

    /**
     * 处理退出编辑状态的消息
     *
     * @param pictureEditMessageDTO 前端发送的消息
     * @param session               当前会话
     * @param pictureId             图片 ID
     * @param user                  当前用户
     */
    public void handleExitEditMessage(PictureEditMessageDTO pictureEditMessageDTO, WebSocketSession session, Long pictureId, User user) throws Exception {
        // 获取当前编辑者
        Long editingUserId = pictureEditingUsers.get(pictureId);
        // 判断当前用户是否是当前正在编辑的图片的编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 移除当前编辑用户的编辑状态
            pictureEditingUsers.remove(pictureId);
            // 向会话内的全体用户发送消息
            PictureEditMessageVO pictureEditMessageVO = new PictureEditMessageVO();
            pictureEditMessageVO.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            String message = String.format("%s 退出了图片编辑", user.getUserName());
            pictureEditMessageVO.setMessage(message);
            pictureEditMessageVO.setUser(userService.getUserVO(user));
            // 对参与该会话的用户发送广播
            broadcastByPictureId(pictureId, pictureEditMessageVO);
        }
    }

    /**
     * 对同一图片编辑的用户进行广播（ 排除某个会话 ）
     *
     * @param pictureId            图片 ID
     * @param pictureEditMessageVO 图片编辑消息响应
     * @param excludeSession       排除的会话 Session
     */
    private void broadcastByPictureId(Long pictureId, PictureEditMessageVO pictureEditMessageVO, WebSocketSession excludeSession) throws Exception {
        // 1. 拿到所有图片的会话集合
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        // 2. 判断是否发送会话
        if (CollUtil.isNotEmpty(webSocketSessions)) {
            // 因为 pictureEditMessageVO 中的 UserVO 中 id 为 Long 类型
            // 在进行 JSONUtil 转换时会造成精度丢失问题， 因此需要配置 Jackson 序列化器
            ObjectMapper objectMapper = getObjectMapper();

            // 将 pictureEditMessageVO 转为 JSON 发送消息
            String message = objectMapper.writeValueAsString(pictureEditMessageVO);
            TextMessage textMessage = new TextMessage(message);
            // 3. 遍历会话集合，发送消息
            for (WebSocketSession webSocketSession : webSocketSessions) {
                // 排除某个会话
                if (Objects.equals(webSocketSession, excludeSession)) {
                    continue;
                }
                if (webSocketSession.isOpen()) {
                    // 发送消息
                    webSocketSession.sendMessage(textMessage);
                }
            }
        }
    }

    /**
     * 对同一图片编辑的用户进行广播 （ 全体广播 ）
     *
     * @param pictureId            图片 ID
     * @param pictureEditMessageVO 图片编辑消息响应
     */
    private void broadcastByPictureId(Long pictureId, PictureEditMessageVO pictureEditMessageVO) throws Exception {
        // 对全体图片编辑的用户进行广播
        broadcastByPictureId(pictureId, pictureEditMessageVO, null);
    }

    /**
     * 获取 ObjectMapper 实例，处理 Long 类型序列化时 JSON 精度丢失问题
     *
     * @return ObjectMapper 实例
     */
    public static ObjectMapper getObjectMapper() {
        // 创建 ObjectMapper 实例，用于将对象序列化为 JSON 字符串
        ObjectMapper objectMapper = new ObjectMapper();
        // 创建 SimpleModule 模块，用于自定义序列化规则
        SimpleModule module = new SimpleModule();
        // 添加 Long 类型的序列化器，将其序列化为字符串以避免精度丢失
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        // 将自定义的模块注册到 ObjectMapper 中
        objectMapper.registerModule(module);
        return objectMapper;
    }

}
